package com.songcha.library_base.mvvm.base

import android.app.Application
import android.content.Context
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.songcha.library_base.R
import com.songcha.library_base.interfaces.IApiResult
import com.songcha.library_base.interfaces.ILoadData
import com.songcha.library_base.interfaces.IResult
import com.songcha.library_base.mvvm.exception.CodeException
import com.songcha.library_base.mvvm.exception.LoginTokenInvalidException
import com.songcha.library_base.mvvm.exception.NoDataException
import com.songcha.library_base.mvvm.exception.NoNetworkException
import com.songcha.library_common.BuildConfig
import com.songcha.library_common.LibraryCommon
import com.songcha.library_common.util.ToastUtil
import com.songcha.library_network.api.ApiCode
import com.songcha.library_network.bean.BaseBean
import com.songcha.library_network.bean.BaseListDataBean
import com.songcha.library_network.util.NetworkUtil
import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.functions.Function
import io.reactivex.schedulers.Schedulers
import okhttp3.Route
import retrofit2.HttpException
import java.lang.reflect.ParameterizedType
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.security.auth.login.LoginException


/*enum class NetworkState{
    SUCCESS,
    ERROR,
    NO_NETWORK,
}*/

fun <O:BaseBean,R:BaseBean> Observable<O>.beanFlatMap(block:(O)->Observable<R>):Observable<R>{
    return this.flatMap(object : Function<O, Observable<R>> {
        override fun apply(t:  O): Observable<R> {
            if(t.code== ApiCode.API_SUCCESS_CODE){
                return block(t)
            }else{
                return Observable.error(CodeException(t.code,t.msg))
            }
        }

    })
}

abstract class BaseViewModel<RP:BaseRepository>(application: Application):AndroidViewModel(application), ILoadData {
    //val network_state : MutableLiveData<NetworkState> = MutableLiveData<NetworkState>()
    var error:Throwable?=null
    val state : MutableLiveData<LoadState> = MutableLiveData<LoadState>()
    val dialogState : MutableLiveData<Boolean> = MutableLiveData<Boolean>()
    protected var repository:RP
    //prevent init many times
    var isInit=false
    protected var apiErrorListener:IOnApiErrorListener?=null

    enum class LoadState{
        LOADING,
        LOAD_SUCCESS,
        LOAD_ERROR,
        LOAD_TIMEOUT,
        LOAD_NO_DATA,
        LOAD_NO_NETWORK,
        LOAD_CONNECT_ERROR,
    }

    interface IOnApiErrorListener{
        fun onApiError(e: Throwable)
    }

    init {
        val rpCls=(this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<RP>
        val rpConstructor = rpCls.getConstructor()
        repository=rpConstructor.newInstance()
    }

    override fun onLoading() {
        state.value=LoadState.LOADING
    }

    override fun onLoadError(e: Throwable) {
        error=e
        state.value=LoadState.LOAD_ERROR
    }

    override fun onLoadSuccess() {
        state.value=LoadState.LOAD_SUCCESS
    }

    override fun onLoadNoData() {
        state.value=LoadState.LOAD_NO_DATA
    }

    override fun onLoadNoNetwork() {
        state.value=LoadState.LOAD_NO_NETWORK
    }

    override fun onLoadConnectError() {
        state.value=LoadState.LOAD_CONNECT_ERROR
    }

    override fun onLoadTimeOut() {
        state.value=LoadState.LOAD_TIMEOUT
    }

    /*override fun onRetry() {
        state.value=LoadState.LOAD_RETRY
    }*/

    protected open fun onShowLoadingDialog(){
        dialogState.value = true
    }

    protected open fun onHideLoadingDialog(){
        dialogState.value = false
    }

    protected open fun onHandleSubscribe(isSetState:Boolean, isShowLoading:Boolean){
        //perhaps one page load multi api,and single api don't want to show loading
        if(isSetState){
            if(isShowLoading){
                onSetStateStateSubscribe()
            }
        }

    }

    protected open fun onSetStateStateSubscribe(){
        onLoading()
    }

    protected open fun onSetStateNoNetwork(){
        onLoadNoNetwork()
    }

    protected open fun onSetStateConnectError(){
        onLoadConnectError()
    }

    protected open fun onSetStateNoData(){
        onLoadNoData()
    }

    protected open fun onSetStateTimeout(){
        onLoadTimeOut()
    }

    protected open fun onSetStateSuccess(){
        onLoadSuccess()
    }

    protected open fun onSetStateError(e:Throwable){
        onLoadError(e)
    }

    protected open fun onHandleSuccess(data:Any,isSetState:Boolean){
        //check data is empty or not,show nodata when is empty
        if(isSetState){
            if(data is BaseListDataBean<*>){
                if(data.data.size == 0){
                    onSetStateNoData()
                }else{
                    onSetStateSuccess()
                }
            }else if(data is List<*>){
                if(data.size == 0){
                    onSetStateNoData()
                }else{
                    onSetStateSuccess()
                }
            } else{
                onSetStateSuccess()
            }
        }

    }

    protected open fun onHandleError(e:Throwable,isSetState:Boolean,isShowErrorToast:Boolean){
        if(e is NoNetworkException){
            if(isSetState)
                onSetStateNoNetwork()
            if(isShowErrorToast)
                ToastUtil.show(LibraryCommon.getContext().resources.getString(R.string.network_no_network))
        } else if(e is ConnectException || e is UnknownHostException){
            if(!NetworkUtil.isNetworkAvailable(LibraryCommon.getContext())){
                if(isSetState)
                    onSetStateNoNetwork()
                if(isShowErrorToast)
                    ToastUtil.show(LibraryCommon.getContext().resources.getString(R.string.network_no_network))
                return
            }
            if(isSetState)
                onSetStateConnectError()
            if(isShowErrorToast)
                ToastUtil.show(LibraryCommon.getContext().resources.getString(R.string.network_connect_error))
        } else if(e is SocketTimeoutException){
            if(!NetworkUtil.isNetworkAvailable(LibraryCommon.getContext())){
                if(isSetState)
                    onSetStateNoNetwork()
                if(isShowErrorToast)
                    ToastUtil.show(LibraryCommon.getContext().resources.getString(R.string.network_no_network))
                return
            }
            if(isSetState)
                onSetStateTimeout()
            if(isShowErrorToast)
                ToastUtil.show(LibraryCommon.getContext().resources.getString(R.string.network_connect_timeout))
        }else if(e is LoginTokenInvalidException){
            if(isSetState)
                onSetStateError(e)
            /*ARouter.getInstance().build("/business/ClearLoginTokenProviderImpl").navigation()?.let {
                it as IClearLoginTokenProvider
                it.clearLoginToken()
            }*/
        } else if(e is NoDataException){
            if(isSetState)
                onSetStateNoData()
        }else if(e is CodeException){
            if(isSetState)
                onSetStateError(e)
            if(isShowErrorToast && e.msg.isNotEmpty())
                ToastUtil.show(e.msg)
        }else if(e is HttpException){
            if(isSetState)
                onSetStateError(e)
            if(isShowErrorToast && e.message != null)
                ToastUtil.show(e.message!!)
        } else {
            if(isSetState)
                onSetStateError(e)
            if(BuildConfig.DEBUG)
                ToastUtil.show(e.message)
        }
        e.printStackTrace()
        apiErrorListener?.onApiError(e)

    }


    /***
     *  set load state
     */
    protected open fun <T> handleDataObserver(observable: Observable<T>, iResult: IResult<T>,isSetState:Boolean=false, isShowLoading:Boolean=true,isShowErrorToast:Boolean=true){
        observable.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : Observer<T> {
                override fun onSubscribe(d: Disposable) {
                    onHandleSubscribe(isSetState,isShowLoading)
                }

                override fun onNext(t: T & Any) {
                    iResult.onSuccess(t)
                    onHandleSuccess(t as Any,isSetState)
                }

                override fun onError(e: Throwable) {
                    iResult.onError(e)
                    onHandleError(e,isSetState,isShowErrorToast)
                }

                override fun onComplete() {
                    //onLoadSuccess()
                }

            })
    }

    /***
     *  set load state
     *  isSetLoadState:whether update load state
     */

    protected open fun <T: BaseBean> handleApiDataObserver(
        observable: Observable<T>,
        iResult: IApiResult<T>,
        isSetState:Boolean=false, isShowLoading:Boolean=true,
        isShowErrorToast:Boolean=true){
        observable.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : Observer<T> {
                override fun onSubscribe(d: Disposable) {
                    onHandleSubscribe(isSetState,isShowLoading)
                }

                override fun onNext(t: T) {
                    when (t.code) {
                        //cause struct maybe build by self,so 0 is default success
                        0,ApiCode.API_SUCCESS_CODE -> {
                            iResult.onSuccess(t)
                            onHandleSuccess(t,isSetState)
                        }
                        ApiCode.API_TOKEN_INVALID_CODE->{
                            //handleApiError(t,isShowErrorToast)
                           // onHandleError(LoginTokenInvalidException(),isShowErrorToast)
                            //handleTokenInvalid(t,isShowErrorToast)
                            val e = LoginTokenInvalidException(t.msg ?: "")
                            iResult.onError(e)
                            onHandleError(e,isSetState,isShowErrorToast)
                        }
                        else -> {
                            val e = CodeException(t.code,t.msg ?: "")
                            //handleError(e,isShowErrorToast)
                            //handleApiError(t,isShowErrorToast)
                            iResult.onError(e)
                            onHandleError(e,isSetState,isShowErrorToast)
                        }

                    }
                }

                override fun onError(e: Throwable) {
                    //handleApiNoResponseError(e,isShowErrorToast)
                   // handleError(e,isShowErrorToast)
                    e.printStackTrace()
                    iResult.onError(e)
                    onHandleError(e,isSetState,isShowErrorToast)
                }

                override fun onComplete() {
                    //onLoadSuccess()
                }

            })
    }



    /*protected fun handleError(e:Throwable,isShowErrorToast:Boolean){


    }
*/
   /* protected fun handleApiError(bean: BaseBean,isShowErrorToast:Boolean){
        if(isShowErrorToast)
            ToastUtil.show(bean.msg)
    }*/

   /* protected fun handleTokenInvalid(){
        ARouter.getInstance().build("/business/ClearLoginTokenProviderImpl").navigation()?.let {
            it as IClearLoginTokenProvider
            it.clearLoginToken()
        }
    }


    protected fun handleApiNoResponseError(e:Throwable,isShowErrorToast:Boolean){
        if(isShowErrorToast){
            if(e is NoNetworkException){
                ToastUtil.show("无网络连接")
            }else if(e is ConnectException){
                ToastUtil.show("网络连接失败")
            }
        }

    }*/


}




